﻿using System;
using System.Linq;

using CreativeMinds.StopForumSpam;
using CreativeMinds.StopForumSpam.Responses;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Events;
using mvcForum.Core.Interfaces.AddOns;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Interfaces.Events;
using SimpleLocalisation;
using mvcForum.Core.Interfaces.Services;

namespace mvcForum.AddOns.StopForumSpam {

	public class StopForumSpamAddOn : IAntiSpamAddOn, IAsyncEventListener<NewPostEvent>, IAsyncEventListener<NewTopicEvent>, IAsyncEventListener<PostUpdatedEvent>, IAsyncEventListener<TopicUpdatedEvent>, IEventListener<NewUserEvent> {
		private readonly IConfiguration config;
		private readonly StopForumSpamConfiguration addOnConfig;
		private readonly ILogger logger;
		private readonly IContext context;
		private readonly IRepository<Post> postRepo;
		private readonly IRepository<Topic> topicRepo;
		private readonly IAsyncTask task;
		private readonly TextManager textManager;

		public StopForumSpamAddOn(StopForumSpamConfiguration addOnConfig, IConfiguration config, ILogger logger, IContext context, IRepository<Post> postRepo, IRepository<Topic> topicRepo, IAsyncTask task, TextManager textManager) {
			this.config = config;
			this.addOnConfig = addOnConfig;
			this.logger = logger;
			this.context = context;
			this.postRepo = postRepo;
			this.topicRepo = topicRepo;
			this.task = task;
			this.textManager = textManager;
		}

		#region Queue
		public void Queue(TopicUpdatedEvent payload) {
			this.Enqueue(payload);
		}

		public void Queue(PostUpdatedEvent payload) {
			this.Enqueue(payload);
		}

		public void Queue(NewTopicEvent payload) {
			this.Enqueue(payload);
		}

		public void Queue(NewPostEvent payload) {
			this.Enqueue(payload);
		}

		private void Enqueue(Object payload) {
			if (this.addOnConfig.Enabled && this.addOnConfig.MarkAsSpamOnHit) {
				task.Execute(this, payload, this.addOnConfig.Delay);
			}
		}
		#endregion

		#region Handle
		public void Handle(Object payload) {
			if (this.addOnConfig.Enabled && this.addOnConfig.MarkAsSpamOnHit) {
				if (payload is TopicUpdatedEvent) {
					this.Handle((TopicUpdatedEvent)payload);
				}
				else if (payload is PostUpdatedEvent) {
					this.Handle((PostUpdatedEvent)payload);
				}
				else if (payload is NewTopicEvent) {
					this.Handle((NewTopicEvent)payload);
				}
				else if (payload is NewPostEvent) {
					this.Handle((NewPostEvent)payload);
				}
				else {
					throw new ApplicationException("Unknown payload!");
				}
			}
		}

		public void Handle(TopicUpdatedEvent payload) {
			if (this.addOnConfig.Enabled && this.addOnConfig.MarkAsSpamOnHit) {
				Topic topic = this.topicRepo.Read(payload.TopicId);

				// Not already deleted or quarantined? Should the updated topic be put in quarantine/marked as spam?
				if ((topic.Flag & TopicFlag.Deleted) == 0 &&
					(topic.Flag & TopicFlag.Quarantined) == 0 &&
					this.IsSpam(topic.Posts.OrderBy(p => p.Posted).First())) {

					if (this.addOnConfig.MarkAsSpamOnHit) {
						topic.SetFlag(TopicFlag.Quarantined);

						this.MarkAsSpam(topic.Posts.OrderBy(p => p.Posted).First(), payload);

						this.context.SaveChanges();
					}
				}
			}
		}

		public void Handle(NewTopicEvent payload) {
			if (this.addOnConfig.Enabled && this.addOnConfig.MarkAsSpamOnHit) {
				Topic topic = this.topicRepo.Read(payload.TopicId);

				// Not already deleted or quarantined? Should the updated topic be put in quarantine/marked as spam?
				if ((topic.Flag & TopicFlag.Deleted) == 0 &&
					(topic.Flag & TopicFlag.Quarantined) == 0 &&
					this.IsSpam(topic.Posts.OrderBy(p => p.Posted).First())) {

					if (this.addOnConfig.MarkAsSpamOnHit) {
						topic.SetFlag(TopicFlag.Quarantined);

						this.MarkAsSpam(topic.Posts.OrderBy(p => p.Posted).First(), payload);

						this.context.SaveChanges();
					}
				}
			}
		}

		public void Handle(PostUpdatedEvent payload) {
			if (this.addOnConfig.Enabled && this.addOnConfig.MarkAsSpamOnHit) {
				Post post = this.postRepo.Read(payload.PostId);

				// Not already deleted or quarantined? Should the updated post be put in quarantine/marked as spam?
				if ((post.Flag & PostFlag.Deleted) == 0 &&
					(post.Flag & PostFlag.Quarantined) == 0 &&
					this.IsSpam(post)) {

					if (this.addOnConfig.MarkAsSpamOnHit) {
						post.SetFlag(PostFlag.Quarantined);

						this.MarkAsSpam(post, payload);

						this.context.SaveChanges();
					}
				}
			}
		}

		public void Handle(NewPostEvent payload) {
			if (this.addOnConfig.Enabled && this.addOnConfig.MarkAsSpamOnHit) {
				Post post = this.postRepo.Read(payload.PostId);

				// Not already deleted or quarantined? Should the updated post be put in quarantine/marked as spam?
				if ((post.Flag & PostFlag.Deleted) == 0 &&
					(post.Flag & PostFlag.Quarantined) == 0 &&
					this.IsSpam(post)) {

					if (this.addOnConfig.MarkAsSpamOnHit) {
						post.SetFlag(PostFlag.Quarantined);

						this.MarkAsSpam(post, payload);
						
						this.context.SaveChanges();
					}
				}
			}
		}

		public void Handle(NewUserEvent payload) {
			// Is this add-on enabled? Was the user already identified as a bot?
			if (this.addOnConfig.CheckNewUsers && !payload.Bot) {
				Client client = new Client(this.addOnConfig.Key);
				Response response = client.Check(payload.Username, payload.EmailAddress, payload.IPAddress);
				if (response.Success) {
					payload.Bot = response.ResponseParts.Where(rp => rp.Appears > 0).Any();
				}
			}
		}
		#endregion

		private void MarkAsSpam(Post post, InformationBearingEvent payload) {
			post.EditReason += Environment.NewLine + this.textManager.Get("QuarantineReason", ns: "mvcForum.AddOns.StopForumSpam");

			payload.AddFeedback("QuarantineFeedback", "mvcForum.AddOns.StopForumSpam", null);
		}

		private Boolean IsSpam(Post post) {
			Client client = new Client(this.addOnConfig.Key);
			Response response = client.Check(post.Author.Name, post.Author.EmailAddress, post.IP);
			// TODO: Too simple? Need frequency too? And last seen??
			return response.ResponseParts.Where(rp => rp.Appears > 0).Any();
		}

		public Boolean RunAsynchronously {
			get {
				return false;
			}
		}

		public Byte Priority {
			get {
				return (Byte)EventPriority.High;
			}
		}

		public Boolean UniqueEvent {
			get {
				return false;
			}
		}
	}
}